/*
 * Copyright 2011 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.dubbo.governance.web.governance.module.screen;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.dubbo.governance.service.ProviderService;
import com.alibaba.dubbo.governance.service.RouteService;
import com.alibaba.dubbo.governance.web.common.module.screen.Restful;
import com.alibaba.dubbo.registry.common.domain.Access;
import com.alibaba.dubbo.registry.common.domain.Route;
import com.alibaba.dubbo.registry.common.route.RouteRule;
import com.alibaba.dubbo.registry.common.route.RouteRule.MatchPair;
import com.alibaba.dubbo.registry.common.util.Tool;

/**
 * Providers. URI: /services/$service/accesses
 * 
 * @author william.liangf
 * @author ding.lid
 * @author tony.chenl
 */
public class Accesses extends Restful {

    @Autowired
    private RouteService           routeService;

    @Autowired
    private ProviderService         providerService;

    public void index(Map<String, Object> context) throws Exception {
        String service = (String) context.get("service");
        String address = (String) context.get("address");
        address = Tool.getIP(address);
        List<Route> routes;
        if (service != null && service.length() > 0) {
            routes = routeService.findForceRouteByService(service);
        } else if (address != null && address.length() > 0) {
            routes = routeService.findForceRouteByAddress(address);
        } else {
            routes = routeService.findAllForceRoute();
        }
        List<Access> accesses = new ArrayList<Access>();
        if(routes == null){
            context.put("accesses", accesses);
            return;
        }
        for(Route route :routes){
            Map<String, MatchPair> rule = RouteRule.parseRule(route.getMatchRule());
            MatchPair pair = rule.get("consumer.host");
            if(pair != null){
                for(String host : pair.getMatches()){
                    Access access = new Access();
                    access.setAddress(host);
                    access.setService(route.getService());
                    access.setAllow(false);
                    accesses.add(access);
                }
                for(String host : pair.getUnmatches()){
                    Access access = new Access();
                    access.setAddress(host);
                    access.setService(route.getService());
                    access.setAllow(true);
                    accesses.add(access);
                } 
            }
        }
        context.put("accesses", accesses);
    }

    public void add(Map<String, Object> context) {
        List<String> serviceList = Tool.sortSimpleName(providerService.findServices());
        context.put("serviceList", serviceList);
    }

    private static final Pattern IP_PATTERN       = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3}$");

    private static final Pattern LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$");
    private static final Pattern ALL_IP_PATTERN   = Pattern.compile("0{1,3}(\\.0{1,3}){3}$");

    public boolean create(Map<String, Object> context) throws Exception {
        String addr = (String) context.get("consumerAddress");
        String services = (String) context.get("service"); 
        Set<String> consumerAddresses = toAddr(addr);
        Set<String> aimServices = toService(services);
        for(String aimService : aimServices) {
            boolean isFirst = false;
            List<Route> routes = routeService.findForceRouteByService(aimService);
            Route route = null;
            if(routes==null||routes.size()==0){
                isFirst = true;
                route  = new Route();
                route.setService(aimService);
                route.setForce(true);
                route.setName(aimService+" blackwhitelist");
                route.setFilterRule("false");
                route.setEnabled(true);
            }else{
                route = routes.get(0);
            }
            Map<String, MatchPair> when = null;
            MatchPair matchPair = null;
            if(isFirst){
                when = new HashMap<String, MatchPair>();
                matchPair = new MatchPair(new HashSet<String>(),new HashSet<String>());
                when.put("consumer.host", matchPair);
            }else{
                when = RouteRule.parseRule(route.getMatchRule());
                matchPair = when.get("consumer.host");
            }
            for (String consumerAddress : consumerAddresses) {
                if(Boolean.valueOf((String) context.get("allow"))){
                    matchPair.getUnmatches().add(Tool.getIP(consumerAddress));
                    
                }else{
                    matchPair.getMatches().add(Tool.getIP(consumerAddress));
                }
            }
            StringBuilder sb = new StringBuilder();
            RouteRule.contidionToString(sb,when);
            route.setMatchRule(sb.toString());
            route.setUsername(operator);
            if(isFirst){
                routeService.createRoute(route);
            }else{
                routeService.updateRoute(route);
            }
            
        }
        return true;
    }
    
    private Set<String> toAddr(String addr) throws IOException{
        Set<String> consumerAddresses = new HashSet<String>();
        BufferedReader reader = new BufferedReader(new StringReader(addr));
        while (true) {
            String line = reader.readLine();
            if (null == line)
                break;
            
            String[] split = line.split("[\\s,;]+");
            for (String s : split) {
                if (s.length() == 0)
                    continue;
                if (!IP_PATTERN.matcher(s).matches()) {
                    throw new IllegalStateException("illegal IP: " + s);
                }
                if (LOCAL_IP_PATTERN.matcher(s).matches() || ALL_IP_PATTERN.matcher(s).matches()) {
                    throw new IllegalStateException("local IP or any host ip is illegal: " + s);
                }

                consumerAddresses.add(s);
            }
        }
        return consumerAddresses;
    }
        
    private Set<String> toService(String services) throws IOException{
        Set<String> aimServices  = new HashSet<String>();
        BufferedReader reader = new BufferedReader(new StringReader(services));
        while (true) {
            String line = reader.readLine();
            if (null == line)
                break;
            
            String[] split = line.split("[\\s,;]+");
            for (String s : split) {
                if (s.length() == 0)
                    continue;
                aimServices.add(s);
            }
        }
        return aimServices;
    }

    /**
     * 删除动作
     * @throws ParseException 
     */
    public boolean delete(Map<String, Object> context) throws ParseException {
        String accesses = (String) context.get("accesses");
        String[] temp = accesses.split(" ");
        Map<String,Set<String>> prepareToDeleate = new HashMap<String,Set<String>>();
        for(String s : temp){
        	String service = s.split("=")[0];
            String address = s.split("=")[1];
            Set<String> addresses = prepareToDeleate.get(service);
            if(addresses == null){
            	prepareToDeleate.put(service, new HashSet<String>());
            	addresses = prepareToDeleate.get(service);
            }
            addresses.add(address);
        }
        for(Entry<String, Set<String>> entry : prepareToDeleate.entrySet()){
            
            String service = entry.getKey();
            List<Route> routes = routeService.findForceRouteByService(service);
            if(routes == null || routes.size() == 0){
                continue;
            }
            for(Route blackwhitelist : routes){
                MatchPair pairs = RouteRule.parseRule(blackwhitelist.getMatchRule()).get("consumer.host");
                Set<String> matches = new HashSet<String>();
                matches.addAll(pairs.getMatches());
                Set<String> unmatches = new HashSet<String>();
                unmatches.addAll(pairs.getUnmatches());
                for(String pair : pairs.getMatches()){
                	for(String address : entry.getValue()){
                		if(pair.equals(address)){
                            matches.remove(pair);
                            break;
                        }
                	}
                }
                for(String pair : pairs.getUnmatches()){
                	for(String address : entry.getValue()){
                		 if(pair.equals(address)){
                             unmatches.remove(pair);
                             break;
                         }
                	}
                }
                if(matches.size()==0 && unmatches.size()==0){
                    routeService.deleteRoute(blackwhitelist.getId());
                }else{
                    Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
                    condition.put("consumer.host", new MatchPair(matches,unmatches));
                    StringBuilder sb = new StringBuilder();
                    RouteRule.contidionToString(sb,condition);
                    blackwhitelist.setMatchRule(sb.toString());
                    routeService.updateRoute(blackwhitelist);
                }
            }
            
        }
        return true;
    }

    public void show(Map<String, Object> context) {
    }

    public void edit(Map<String, Object> context) {
    }

    public String update(Map<String, Object> context) {
        return null;
    }

}
